<?php

/**
 * Implementation of OAuth for the authentication of API requests to/from our server.
 * 
 * @version $Id$
 * @author Marc Worrell <marc@mediamatic.nl>
 * @copyright (c) 2007 Mediamatic Lab
 * @date  Oct 31, 2007 1:56:56 PM
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
 
/**
 * TODO:
 * 
 * - create and add iRequester interface for signing outgoing messages
 * - cleanup code and remove non used old stuff
 * - add support for multiple parameters with the same name (like arrays)
 * - make a more abstract OAuthStore and add support for implementing specific ones
 * - package the whole thing for publication (select license)
 */

require_once ANYMETA_CORE . 'interfaces/Module.php';
require_once ANYMETA_CORE . 'interfaces/iAuthExtender.class.php';

require_once dirname(__FILE__) . '/core/OAuthRequester.php';
require_once dirname(__FILE__) . '/core/OAuthRequestVerifier.php';
require_once dirname(__FILE__) . '/core/OAuthServer.php';

class OAuth extends Module implements iAuthExtender
{
	function description ()
	{
		return 'OAuth - security for API usage by and of external applications';
	}
	
	function author ()
	{
		return 'Marc Worrell';
	}
	
	function version ()
	{
		return '0.3.2';
	}

	function enable ()
	{
		return true;
	}

	function disable ()
	{
		return true;
	}
	
	function install ()
	{
		$store = OAuthStore::instance();
		$store->install();
		
		any_config_insert(
				'debugging.oauth.show_tokens',
				0, 
				'Show token keys and secrets.',
				'0,1');

		any_config_insert(
				'debugging.oauth.log_menu',
				0, 
				'Show the log menu.',
				'0,1');

		any_config_insert(
				'debugging.oauth.log',
				0, 
				'Log all incoming OAuth requests.',
				'0,1');

		return true;
	}
	
	function uninstall ()
	{
		return true;
	}
	
	function upgrade ( $old_version )
	{
		$this->install();
		return true;
	}
	
	
	/**
	 * Show a page where people can see a description, a form to register for
	 * an application key/secret and all the urls for the OAuth communication.
	 * 
	 * (Authorization Endpoint URL, API Endpoint URL, signature algorithms)
	 */	
	function httpRequest ()
	{
		$smarty = session_smarty();
		$rsrc	= any_tpl_rsrc($smarty, array('file'=>'pag_oauth.tpl'));
		$smarty->display($rsrc);
	}


	/**
	 * Show links to registration form and list of consumers
	 */	
	function httpRequest_server ()
	{
		$smarty = session_smarty();
		$rsrc	= any_tpl_rsrc($smarty, array('file'=>'pag_oauth_server.tpl'));
		$smarty->display($rsrc);
	}


	/**
	 * Registration form for a consumer key and secret.
	 */	
	function httpRequest_server_register ()
	{
		global $any_auth;
		
		$any_auth->needAuth();
		
		$store  = OAuthStore::instance(); 
		$smarty = session_smarty();
		if (!empty($_POST))
		{
			// We got a registration request, or an update of an existing registration
			// Note that all our values are tainted, so just fetch the post as an array and continue.
			$post  = $_POST->asArray();
			$key   = $store->updateConsumer($post, $any_auth->getUserId(), $any_auth->isSysadmin());
			
			if (empty($post['id']))
			{
				// Show the new consumer key and secret
				any_redirect('module/OAuth/server/register?consumer_key='.$key);
			}
			else
			{
				any_redirect('module/OAuth/server/list');
			}
		}

		if (!empty($_GET['consumer_key']))
		{
			try 
			{
				$smarty->assign('consumer', $store->getConsumer($_GET['consumer_key']));
			}
			catch (OAuthException $e)
			{
				$smarty->assign('errror', 'NOTFOUND');
			}
		}
		
		$rsrc	= any_tpl_rsrc($smarty, array('file'=>'pag_oauth_server_register.tpl'));
		$smarty->display($rsrc);
	}


	/**
	 * List all consumer keys that are registered on our site.
	 * Enable/disable/delete consumer keys.
	 */	
	function httpRequest_server_list ()
	{
		global $any_auth;
		
		$any_auth->needAuth();
		
		$smarty = session_smarty();
		$store  = OAuthStore::instance();
		$smarty->assign('consumers', $store->listConsumers($any_auth->getUserId()));
		$rsrc	= any_tpl_rsrc($smarty, array('file'=>'pag_oauth_server_list.tpl'));
		$smarty->display($rsrc);
	}

	
	/**
	 * Delete a consumer key from the OAuth server
	 */	
	function httpRequest_server_delete ()
	{
		global $any_auth;
		
		$any_auth->needAuth();
		
		$smarty = session_smarty();
		$store  = OAuthStore::instance();

		try
		{
			if (	array_key_exists('consumer_key', $_POST)
				&&	array_key_exists('confirm',      $_POST))
			{
				$store->deleteConsumer($_POST->consumer_key->getRawUnsafe(), $any_auth->getUserId());
				any_redirect('module/OAuth/server/list');
			}
			else
			{
				$consumer = $store->getConsumer($_GET->consumer_key->getRawUnsafe(), $any_auth->getUserId());
				if ($consumer['user_id'] != $any_auth->getUserId())
				{
					throw new OAuthException('Delete permission denied');
				}
				$count    = $store->countConsumerAccessTokens($_GET->consumer_key->getRawUnsafe());
				$smarty->assign('consumer', $consumer);
				$smarty->assign('token_ct', $count);
			}
		}
		catch (OAuthException $e)
		{
			$smarty->assign('error', 'NOCONSUMER');
		}
		
		$rsrc = any_tpl_rsrc($smarty, array('file'=>'pag_oauth_server_delete.tpl'));
		$smarty->display($rsrc);
	}
	

	/**
	 * List all consumer tokens that are registered on our site.
	 * Enable/disable/delete tokens.
	 */	
	function httpRequest_server_token_list ()
	{
		global $any_auth;
		
		$any_auth->needAuth();
		
		$smarty = session_smarty();
		$store  = OAuthStore::instance();
		$smarty->assign('tokens', $store->listConsumerTokens($any_auth->getUserId()));
		$rsrc	= any_tpl_rsrc($smarty, array('file'=>'pag_oauth_server_token_list.tpl'));
		$smarty->display($rsrc);
	}
	
	/**
	 * Delete an access token
	 */	
	function httpRequest_server_token_revoke ()
	{
		global $any_auth;
		
		$any_auth->needAuth();
		
		$smarty = session_smarty();
		$store  = OAuthStore::instance();

		if (array_key_exists('token', $_POST) && array_key_exists('confirm', $_POST))
		{
			$store->deleteConsumerAccessToken($_POST->token->getRawUnsafe(), $any_auth->getUserId());
			any_redirect('module/OAuth/server/token/list');
		}
		else
		{
			try
			{
				$token = $store->getConsumerAccessToken($_GET->token->getRawUnsafe(), $any_auth->getUserId());
				$smarty->assign('token', $token);
			}
			catch (OAuthException $e)
			{
				$smarty->assign('error', 'NOTOKEN');
			}
		}
		
		$rsrc = any_tpl_rsrc($smarty, array('file'=>'pag_oauth_server_token_revoke.tpl'));
		$smarty->display($rsrc);
	}
	


	/**
	 * Show links to list of servers (that we are a consumer of) and to the form
	 * for registering us as a consumer of another site.
	 */	
	function httpRequest_consumer ()
	{
		$smarty = session_smarty();
		$rsrc	= any_tpl_rsrc($smarty, array('file'=>'pag_oauth_consumer.tpl'));
		$smarty->display($rsrc);
	}



	/**
	 * Register a new server, so that we can become a consumer of that server.
	 */	
	function httpRequest_consumer_register ()
	{
		global $any_auth;
		
		$any_auth->needAuth();
		
		$smarty = session_smarty();
		$store  = OAuthStore::instance(); 
		if (!empty($_POST))
		{
			$server = $_POST->asArray();
			try
			{
				$consumer_key = $store->updateServer($server, $any_auth->getUserId(), $any_auth->isSysadmin());
				any_redirect('module/OAuth/consumer/connect?consumer_key='.rawurlencode($consumer_key));
			}
			catch (OAuthException $e)
			{
				$smarty->assign('error', 'ERROR');
			}
		}
		else if (array_key_exists('consumer_key', $_REQUEST))
		{
			try
			{
				$server = $store->getServer($_REQUEST->consumer_key->getRawUnsafe());
				
				if ($server['user_id'] != $any_auth->getUserId() && !$any_auth->isSysadmin())
				{
					$server = array('consumer_key' => $_REQUEST->consumer_key->getRawUnsafe());
					$smarty->assign('error', 'NOPERMISSION');
				}
			}
			catch (OAuthException $e)
			{
				$smarty->assign('error', 'NOSERVER');
				$server = array('consumer_key' => $_REQUEST->consumer_key->getRawUnsafe());
			}
		}
		else
		{
			$server = array();
		}
		
		$smarty->assign('server', $server);

		$rsrc   = any_tpl_rsrc($smarty, array('file'=>'pag_oauth_consumer_register.tpl'));
		$smarty->display($rsrc);
	}


	
	/**
	 * Delete a server from the OAuth consumer registry
	 */	
	function httpRequest_consumer_delete ()
	{
		global $any_auth;
		
		$any_auth->needAuth();
		
		$smarty = session_smarty();
		$store  = OAuthStore::instance();

		try
		{
			if (	array_key_exists('consumer_key', $_POST)
				&&	array_key_exists('confirm',      $_POST))
			{
				$store->deleteServer($_POST->consumer_key->getRawUnsafe(), $any_auth->getUserId());
				any_redirect('module/OAuth/consumer/list');
			}
			else
			{
				$server = $store->getServer($_GET->consumer_key->getRawUnsafe(), $any_auth->getUserId());
				if ($server['user_id'] != $any_auth->getUserId())
				{
					throw new OAuthException('Delete permission denied');
				}
				$count = $store->countServerTokens($_GET->consumer_key->getRawUnsafe());
				$smarty->assign('server',   $server);
				$smarty->assign('token_ct', $count);
			}
		}
		catch (OAuthException $e)
		{
			$smarty->assign('error', 'NOSERVER');
		}
		
		$rsrc = any_tpl_rsrc($smarty, array('file'=>'pag_oauth_consumer_delete.tpl'));
		$smarty->display($rsrc);
	}


	/**
	 * List all servers that are registered on our site.
	 * Enable/disable servers.
	 */	
	function httpRequest_consumer_list ()
	{
		global $any_auth;
		
		$any_auth->needAuth();
		
		if (isset($_GET['q_mm']))
		{
			$q_mm = $_GET->q_mm->getRawUnsafe();
		}
		else
		{
			$q_mm = '';
		}

		$store	= OAuthStore::instance();
		$smarty = session_smarty();
		$smarty->assign('servers', $store->listServers($q_mm, $any_auth->getUserId()));
		$rsrc	= any_tpl_rsrc($smarty, array('file'=>'pag_oauth_consumer_list.tpl'));
		$smarty->display($rsrc);
	}


	/**
	 * List all servers that the current user can access.
	 */	
	function httpRequest_consumer_token_list ()
	{
		global $any_auth;
		
		$any_auth->needAuth();

		$store	= OAuthStore::instance();
		$smarty = session_smarty();
		$smarty->assign('tokens', $store->listServerTokens($any_auth->getUserId()));
		$rsrc	= any_tpl_rsrc($smarty, array('file'=>'pag_oauth_consumer_token_list.tpl'));
		$smarty->display($rsrc);
	}
	
	/**
	 * Delete a token for accessing another site
	 */	
	function httpRequest_consumer_token_remove ()
	{
		global $any_auth;
		
		$any_auth->needAuth();
		
		$smarty = session_smarty();
		$store  = OAuthStore::instance();

		try
		{
			if (	array_key_exists('consumer_key', $_POST)
				&&	array_key_exists('token',        $_POST)
				&&	array_key_exists('confirm',      $_POST))
			{
				$store->deleteServerToken($_POST->consumer_key->getRawUnsafe(), $_POST->token->getRawUnsafe(), $any_auth->getUserId());
				any_redirect('module/OAuth/consumer/token/list');
			}
			else
			{
				$token = $store->getServerToken($_GET->consumer_key->getRawUnsafe(), $_GET->token->getRawUnsafe(), $any_auth->getUserId());
				$smarty->assign('token', $token);
			}
		}
		catch (OAuthException $e)
		{
			$smarty->assign('error', 'NOTOKEN');
		}
		
		$rsrc = any_tpl_rsrc($smarty, array('file'=>'pag_oauth_consumer_token_remove.tpl'));
		$smarty->display($rsrc);
	}
	


	/**
	 * Register a new server, so that we can become a consumer of that server.
	 */	
	function httpRequest_consumer_connect ()
	{
		global $any_auth;
		
		$any_auth->needAuth();
		
		if (isset($_POST['consumer_key']))
		{
			header('content-type: text/plain');
			try
			{
				$this->connectToSite($_POST->consumer_key->getRawUnsafe());
			}
			catch (OAuthException $e)
			{
				var_dump($e);
			}
			die();
		}

		$smarty = session_smarty();
		$store  = OAuthStore::instance(); 
		if (isset($_REQUEST['consumer_key']))
		{
			try
			{
				$server = $store->getServer($_REQUEST->consumer_key->getRawUnsafe());
				$smarty->assign('server', $server);
			}
			catch (OAuthException $e)
			{
				$smarty->assign('error', 'NOSERVER');
			}
		}
		else
		{
			$smarty->assign('error', 'NOSERVER');
		}

		$rsrc = any_tpl_rsrc($smarty, array('file'=>'pag_oauth_consumer_connect.tpl'));
		$smarty->display($rsrc);
	}


	/**
	 * Register a new server, so that we can become a consumer of that server.
	 */	
	function httpRequest_consumer_test ()
	{
		global $any_auth;
		
		$any_auth->needAuth();
		$smarty = session_smarty();

		if (!empty($_POST))
		{
			$url    = @trim($_POST->url->getRawUnsafe());
			$method = $_POST['method'];
			if (!empty($url))
			{
				$params = array();
				$par    = $_POST->par->asArray('getRawUnsafe');				
				$val    = $_POST->val->asArray('getRawUnsafe');				
				$body	= $_POST->body->getRawUnsafe();
				
				if (strlen($body) == 0)
				{
					$body = null;
				}

				foreach ($par as $i => $p)
				{
					if (strlen($p) > 0)
					{
						$params[$p] = $val[$i];
					}
				}
				
				// Start the request
				OAuthRequestLogger::forceLogging();
				$req = new OAuthRequester('http://'.$url, $method, $params, $body);
				OAuthRequestLogger::start($req);
				
				try
				{
					$result = $req->doRequest($any_auth->getUserId());
					$smarty->assign('result', $result);
				}
				catch (OAuthException $e)
				{
					$smarty->assign('error', $e->getMessage());
				}

				OAuthRequestLogger::flush();
				$smarty->assign('log', OAuthRequestLogger::getLog());
			}
		}
		$rsrc = any_tpl_rsrc($smarty, array('file'=>'pag_oauth_consumer_test.tpl'));
		$smarty->display($rsrc);
	}
	

	/* ******************************** HOOKS FOR AUTHENTICATION OF APPLICATIONS **************************** */

	/**
	 * Request Token URL for obtaining a request token.
	 * The request contains a consumer_token and is signed with the consumer_key.
	 * We will return an access token and an access secret
	 */
	function httpRequest_request_token ()
	{
		$server = new OAuthServer();
		$server->requestToken();
	}

	
	/**
	 * User Authorization URL for authentication requests
	 * 
	 * TODO: add a method to ask for the token when the token is not supplied in the GET/POST
	 */
	function httpRequest_authorize ()
	{
		global $any_auth;

		$smarty = session_smarty();
		$store  = OAuthStore::instance();
		$server = new OAuthServer();
		$rsrc	= any_tpl_rsrc($smarty, array('file'=>'pag_oauth_authorize.tpl'));

		try
		{
			$rs = $server->authorizeVerify();

			if (!empty($_POST) && $any_auth->isAuthenticated() && !array_key_exists('password', $_POST))
			{
				// TODO: protect against cross site posting!!!!
				$authorized = array_key_exists('allow', $_POST);
				$server->authorizeFinish($authorized, $any_auth->getUserId());

				// Show the success/failure
				$smarty->assign('finished',   true);
				$smarty->assign('authorized', $authorized);
				
				$rsrc = any_tpl_rsrc($smarty, array('file'=>'pag_oauth_authorize.tpl'));
			}
			else
			{
				// Request authorization
				$rsrc = any_tpl_rsrc($smarty, array('file'=>'pag_oauth_authorize.tpl'));
			}

			// Goto parameter, used for redirection after optional logon
			$goto = 'module/OAuth/authorize?oauth_token='.rawurlencode($rs['token']);
			if (array_key_exists('oauth_callback', $_REQUEST))
			{
				$goto .= '&oauth_callback='.rawurlencode($_REQUEST->oauth_callback->getRawUnsafe());
			}

			$smarty->assign('server', $store->getConsumer($rs['consumer_key']));
			$smarty->assign('token',  $rs);
			$smarty->assign('goto',   htmlspecialchars(any_uri_abs($goto)));
		}
		catch (OAuthException $e)
		{
			// Show a page where the user can enter the token to be verified
			$smarty->assign('error', 'NOVERIFY');
		}
		$smarty->display($rsrc);
	}


	/**
	 * Access Token URL for obtaining an access token.
	 * When the request token has been authorized, then it can be exchanged for
	 * an access token.
	 */
	function httpRequest_access_token ()
	{
		$server = new OAuthServer();
		$server->accessToken();
	}
	
	
	/**
	 * Callback used to obtain user authentication
	 */
	function httpRequest_authorize_request ()
	{
		$server = new OAuthServer();
		$server->requestToken();
	}
	


	/* ******************************** HOOKS FOR AUTHENTICATION OF SERVERS **************************** */
	
	/**
	 * Callback - called when an external server authorizes our access token.
	 * The user is redirected here by the server upon completion of the authorization.
	 * 
	 * Request parameters are oauth_token, consumer_key and usr_id.
	 */
	function httpRequest_consumer_connect_callback ()
	{
		global $any_auth;

		$smarty = session_smarty();

		if (	!$any_auth->isAuthenticated()
			||	!isset($_GET['usr_id'])
			||	$any_auth->getUserId() != $_GET->usr_id->asInt())
		{
			$smarty->assign('error', 'USER');
		}
		else if (array_key_exists('consumer_key', $_GET) && array_key_exists('oauth_token', $_GET))
		{
			try
			{
				OAuthRequester::requestAccessToken(	$_GET->consumer_key->getRawUnsafe(), 
													$_GET->oauth_token->getRawUnsafe(),
													$_GET->usr_id->asInt());
			}
			catch (OAuthException $e)
			{
				// Something wrong with the oauth_token.
				// Could be:
				// 1. Was already ok
				// 2. We were not authorized
				$smarty->assign('error', 'NOACCESS');
			}
		}
		else
		{
			// Got a callback without the oauth_token.. complain to the user...
			$smarty->assign('error', 'NOTOKEN');
		}
		$rsrc = any_tpl_rsrc($smarty, array('file'=>'pag_oauth_consumer_connect_callback.tpl'));
		$smarty->display($rsrc);
	}


	/**
	 * Simple selftester - performs the tests supplied by 
	 */
	public function httpRequest_test ()
	{
		require_once dirname(__FILE__) . '/test/oauth_test.php';
		oauth_test();
	}
	
	
	/**
	 * Show the debug information.  By user, token or consumer key
	 */
	public function httpRequest_log ()
	{
		global $any_auth;
		
		$any_auth->needAuth();
		
		$smarty = session_smarty();
		if (any_config_read('debugging.oauth.log_menu'))
		{
			$store = OAuthStore::instance();
			if (empty($_GET))
			{
				$options = array();
			}
			else
			{
				$options = $_GET->asArray('getRawUnsafe');
			}
			$smarty->assign('log', $store->listLog($options, $any_auth->getUserId()));
		}
		else
		{
			$smarty->assign('error', 'DISABLED');
		}
		$rsrc = any_tpl_rsrc($smarty, array('file'=>'pag_oauth_log.tpl'));
		$smarty->display($rsrc);
	}


	/* ******************************** SUPPORT FUNCTIONS **************************** */
	
	/**
	 * Build a connection with another site (ie. make this site a consumer)
	 * 
	 * @param string consumer_key supplied by remote site
	 * @exception OAuthException on failure
	 */
	protected function connectToSite ( $consumer_key )
	{
		global $any_auth;

		$token 		  = OAuthRequester::requestRequestToken($consumer_key, $any_auth->getUserId());
		$callback_uri = any_uri_append(
								any_uri_abs('module/OAuth/consumer/connect/callback'),
								array(
										'consumer_key'	=> $consumer_key,
										'usr_id'		=> $any_auth->getUserId()
									)
								);
		
		// Now redirect to the autorization uri and get us authorized
		if (!empty($token['authorize_uri']))
		{
			// Redirect to the server, add a callback to our server
			// We will sign with the request token we obtained
			$uri = any_uri_append(	
							$token['authorize_uri'], 
							array(
								'oauth_token'	 => $token['token'],
								'oauth_callback' => $callback_uri
							)
						);
			any_redirect($uri);
		}
		else
		{
			// No authorisation uri, assume we are authorized, exchange request token for access token
			any_redirect(
					any_uri_append(
							$callback_uri,
							array(
								'oauth_token' => $token['token'])
							)
					);
		}
	}
	
	
	/**
	 * Return an unauthorized header, stops the request with a 401 error
	 * 
	 * @param OAuthException e
	 */
	protected function unauthorized ( $e )
	{					
		header('HTTP/1.1 401 Unauthorized');
		header('WWW-Authenticate: OAuth realm=""');
		header('Content-Type: text/plain; charset=utf8');
					
		echo $e->getMessage();
		exit();
	}
	

	/* iAuthExtender interface - we need to log on somebody when there is an OAuth signed request
	 ******************************************************************************************** */

	/**
	 * Called when a new visitor comes to the site, upon creation of the session.
	 * 
	 * Useful when you want to check for autoLogon cookies etc, or when you want
	 * to add special data to the session.
	 * 
	 * @return void
	 */
	public function /*void*/ authVisitor ( )
	{
	}


	/**
	 * Called when a user performs a request on the server.
	 * Useful to keep track if a user is still online or not or to
	 * redirect a user to another url.
	 * 
	 * @param int thg_id	the id of the user
	 * @param string requri	the request uri
	 * @return void
	 */
	public function /*void*/ authUserRequest ( $thg_id, $requri )
	{
		if (OAuthRequestVerifier::requestIsSigned())
		{
			// Requests to the OAuth module use their own verification
			if (strpos($_SERVER['PHP_SELF'], 'module.php/OAuth') === false)
			{
				try
				{
					$req     = new OAuthRequestVerifier();
					$user_id = $req->verify();

					// The session is set to read only, we do not support sessions with OAuth
					session_session_readonly(true);
					
					// Logon as the user, retain old user, do not check the password
					if ($user_id)
					{
						global $any_auth;

						if ($user_id != $any_auth->getUserId())
						{
							$any_auth->logon($user_id, '', false, true);
						}
					}
				}
				catch (OAuthException $e)
				{
					// The request was signed, but failed verification
					$this->unauthorized($e);
				}
			}
		}
	}

	public function /*boolean*/ authUserLogonPre ( $accept, $thg_id, $username )	{ return $accept; }
	public function /*void*/    authUserLogonPost ( $thg_id, $username )	{}
	public function /*void*/    authUserLogoff ( $thg_id, $username )	{}
	public function /*int*/     authCredentialsToUser ( $thg_id, $name, $password )	{ return $thg_id; }
	public function /*boolean*/ authCredentialsCheck ( $accept, $thg_id, $username, $password ) { return $accept; }
	public function /*boolean*/ authCredentialsChangePre ( $accept, $thg_id, $username, $newusername, $newpassword ) { return $accept; }
	public function /*void*/    authCredentialsChangePost ( $thg_id, $username, $newusername, $newpassword ) {}
	public function /*boolean*/ authUserAddPre( $accept, $username ) { return $accept; }
	public function /*void*/    authUserAddPost ( $thg_id, $username ) {}
	public function /*boolean*/ authUserDeletePre ( $accept, $thg_id ) { return $accept; }
	public function /*void*/    authUserDeletePost ( $thg_id ) {}
	public function /*mixed*/   authUserPrefGet ( $thg_id, $pref ) {}
	public function /*void*/    authUserPrefSet ( $thg_id, $pref, $value ) {}
	public function /*array*/   authUserPrefGetAll ( $prefs, $thg_id ) { return $prefs; }
	public function /*void*/    authUserPrefSetAll ( $thg_id, $prefs ) {}
}

/* vi:set ts=4 sts=4 sw=4 binary noeol: */

?>